<style lang="scss">
body {
  overflow: auto;
}
code {
  padding: 0.5rem;
  border: 1px dashed #000;
  vertical-align: 2px;
  margin: 0.5rem;
}
.main.Bill {
  .pretty,
  .select {
    margin-right: 1rem;
  }
}
select {
  background-color: transparent;
}
.main.Bill > .wmax {
  .chart_title {
    font-size: 12px;
    text-align: center;
    color: #999;
    font-weight: 200;
  }
  padding: 0 48px 150px;
  .pretty.p-default {
    margin-left: 16px;
  }
  .pretty .state label {
    font-weight: 200;
  }
  .pretty.p-default input:checked ~ .state label:after {
    background-color: #393 !important;
  }
  .pretty.p-default input:checked ~ .state label:before {
    border-color: #393 !important;
  }
  table {
    text-align: center;
    margin-bottom: 20px;
    font-size: 14px;
    td,
    th {
      padding: 5px;
    }

    tr td:nth-child(4),
    tr td:nth-child(3),
    tr td:nth-child(2),
    tr td:nth-child(1) {
      min-width: 60px;
      font-weight: 200;
    }
    tr:nth-child(1),
    tr:nth-child(2),
    tr td:nth-child(3),
    tr th:nth-child(3),
    tr td:nth-child(2),
    tr th:nth-child(2),
    tr td:nth-child(1),
    tr th:nth-child(1) {
      color: #999;
    }
  }
  .md {
    max-width: 740px;
    margin: auto;
  }
  .bar {
    user-select: none;
    border-top: 1px solid #eee;
    padding-top: 18px;
    border-bottom: 1px solid #eee;
    padding-bottom: 18px;
    background: #f9f9f9;
    margin-left: -48px;
    margin-right: -48px;
    padding-left: 48px;
    padding-right: 48px;
    font-weight: 200;

    .line {
      color: #999;
      float: right;
    }
    .I-save,
    .I-download {
      float: right;
      padding: 0 10px;
      color: #369;
      font-size: 27px;
      margin-top: -10px;
      cursor: pointer;
      &:hover {
        color: #f40;
      }
    }
    .I-download {
      color: #396;
    }
  }
  .tip {
    text-align: center;
    padding: 18px;
    margin-top: 18px;
    color: #392;
    border: 2px dashed #392;
    &.ERR {
      color: #f00;
      border-color: #f00;
    }
  }
  max-width: 100%;
  video {
    width: 100%;
  }
  h1 {
    text-align: center;
    margin-bottom: 39px;
    .I-close {
      float: right;
      height: 32px;
      line-height: 32px;
      cursor: pointer;
      color: #666;
      width: 32px;
      font-size: 20px;
      margin-top: 6px;
      &:hover {
        color: #f40;
      }
    }
  }
  canvas {
    display: block;
    width: 100%;
    height: 90vh;
  }
  .dropzone {
    border-top: 0;
    padding-top: 1px;
  }
  .dropzone .dz-preview.dz-file-preview .dz-image {
    display: none;
  }
  .dz-preview {
    width: 100%;
    height: 92px;
    margin: 0;
  }
  .dropzone .dz-preview .dz-details .dz-size {
    margin-bottom: 8px;
  }
  .dropzone .dz-preview.dz-success .dz-success-mark {
    top: 18px;
  }
  .dropzone .dz-preview.dz-file-preview .dz-details {
    text-align: center;
    padding: 20px;
  }
}
</style>

<template lang="pug">
.main.Bill
  .wmax(v-if="name === 0")
    vue-dropzone#dropzone(
      ref="myVueDropzone",
      :options="dropzoneOptions",
      :use-custom-slot="true"
    )
      .droptip 拖拽到这 或 点此选取 CSV数据文件

    .tip.ERR(v-if="err") {{ err }}
    .md
      :markdown-it(html)
        # 注意事项

        Excel 另存为 CSV 时，文件格式请选择 `CSV UTF-8(逗号分隔)(.csv)`

        # 用法说明

        [点此下载演示文件](https://fxvc.oss-cn-wulanchabu.aliyuncs.com/test.csv)

        以 [谷歌表格](https://docs.google.com/spreadsheets) 为例演示，Excel用法类似。

        新建一个空白表格，然后点击 文件→导入→上传，导入已经有的数据表。

        点击时间列，然后点击 格式→数字→日期，选择类似 2018-8-9 的日期格式。

        修改列名为 **会员号,下单时间,金额,注册时间** 。

        金额为零的行会被忽略。

        其中注册时间这一列可以没有，这时候，程序会把首单时间作为注册时间。

        然后点击 文件→下载为→逗号分隔值（csv），下载保存后拖拽到本页上传框。

        使用演示视频如下 :


      video(controls)
        source(
          src="https://xvc-video.oss-cn-beijing.aliyuncs.com/profile.bill.webm"
        )

      :markdown-it(html)
        ## 相关链接

        * [在线访问 xvcbill.gitee.io](https://xvcbill.gitee.io/)
        * [下载到本地 (打开index.html)](https://gitee.com/xvcbill/xvcbill/repository/archive/master.zip)
        * [源代码](https://gitee.com/xvcbill/xvcbill-src)

  .wmax(v-else)
    .box
      h1
        a.I.I-close(@click="name = 0; qiniu = 0")
        | 投资分析 · {{ name }}
      .bar
        .line 有效行 {{ length }}
          template(v-if="ignore_no_money") · 忽略金额无效的 {{ ignore_no_money }} 行
        .bk
          .select
            label(for="order_group") 订单分组
            select#order_group(v-model.number="订单分组周期")
              option(v-for="i in 所有订单分组周期", :value="i") {{ i }}
          .select
            select#timemode(type="checkbox", v-model.number="timemode")
              option(value="0") 绝对时间
              option(value="1") 相对时间
              option(value="2") 绝对时间左对齐
          .select
            label(for="group_pos") 分组依据
            select#group_pos(v-model.number="group_pos")
              option(v-for="[pos, i] in 分组依据", :value="pos") {{ i }}
          .pretty.p-default
            input#weekuser(type="checkbox", v-model="weekuser")
            .state
              label(for="weekuser") 用户按周分组
          //-
            .select
              select(v-model.number="timemode")
                option(value="0") 绝对月份
                option(value="-1") 绝对月份左对齐
                option(value="1") 相对月份
                option(value="2") 相对周
      .chartLi
</template>

<script lang="coffee">
$ = require 'jquery'
require "./coffee/_lib/jquery/html"
F2 = require "./coffee/_lib/antv"
{zip, chunk, mapint, 绝对时间减法} =  require('./coffee/_lib/lodash')
cmp = (a,b)=> if a[0]>b[0] then 1 else -1
render = (id, csv, timemode)->
#    data = [{ t: '2016-08-01', n: 10 }]
    li = []
    for [m, d] in csv
      d = Object.entries(d)
      if timemode
        d = d.map (x)=>[parseInt(x[0]),x[1]]
      d.sort cmp

      for [mc, v] from d
        #if 相对时间 == timemode
        #  mc = mc - 0
        li.push({
          t:mc
          v
          m
        })

    chart = new F2.Chart({
      id
      pixelRatio: window.devicePixelRatio
    })
    chart.source(
        li
    )
    chart.legend({
        itemWidth:110
        triggerOn:'click'
    })
    chart.axis('t', {
        tickLine: {
          length: 4,
          stroke: '#e8e8e8',
          lineWidth: 1
        }
      })
    chart.line().position('t*v').color('m').shape('smooth')

    chart.tooltip({
      triggerOn:"mousemove"
      alwaysShow:true
      showCrosshairs: true
      custom:true
      onChange: (obj) ->
          legend = chart.get('legendController').legends.top[0]
          tooltipItems = obj.items
          legendItems = legend.items
          map = {}
          legendItems.map((item) ->
              map[item.name] = {...item}
          )
          tooltipItems.map((item) ->
            name = item.name
            value = item.value
            if map[name]
                map[name].value = value
          )
          legend.setItems(Object.values(map))
     onHide: ->
         #legend = chart.get('legendController').legends.top[0]
         #legend.setItems(chart.getLegendItems().country)
       })
    chart.render()

WEEK_MS = 604800000
Papa = require "papaparse"

格式化 = (results, self)->
    self.err = ''
    {errors,data} = results
    if data.length < 2
        err '空文件'
        return

    pos_map = '会员号 金额 下单时间 注册时间'.split(' ')

    for i, i_pos in data.shift()
        p = pos_map.indexOf(i)
        if p + 1
            pos_map[p] = i_pos

    err_li = []
    for i in pos_map
        if typeof(i) == 'string'
            err_li.push i

    {group_pos} = self
    if err_li.length
      if err_li.length == 1 and err_li[0] == '注册时间' and group_pos == 1
        self.分组依据.shift()
        pos_map.pop()
      else
        self.err = "缺少 「 " + err_li.join(' / ') + " 」的 列标注"
        return

    li = []
    user_create_time = {}
    user_id_dict = {}
    user_id_now = 0
    ignore_no_money = 0
    for i in data
      if i.length < pos_map.length
        continue
      t = []
      for pos,_p in pos_map
          if not i[pos]
              break
          val = i[pos].trim()
          if _p == 1 # 忽略金额为0或者无法转为数字的行
            val = val.replace(/,/g,'')-0
            if not val
              ++ignore_no_money
              break
          else if _p > 1
              val = str2date(val).toISOString().slice(0,10)
          t.push val
      if t.length == pos_map.length
          user_id = t[0]
          if not (user_id of user_id_dict)
              user_id_dict[user_id] = ++user_id_now
          t[0] = user_id = user_id_dict[user_id]
          if group_pos != 1
            create_time = t.pop()
            if not (user_id of user_create_time)
                user_create_time[user_id] = create_time
          li.push t
    return [li,user_create_time,ignore_no_money]


month_dict_li = (group_li)->
    t = []
    for k,v of group_li
        i = [k,v]
        t.push i
    t.sort cmp
    return t

group_li = (li)->
  t = {}
  for i in li
    key = i.shift()
    if key of t
        t[key].push i
    else
        t[key] = [i]
  return month_dict_li(t)

str2date = (s)=>
  if s-0
    s = s*1000
  return new Date(s)

日毫秒数 = 86400000

时间周期时长_天=  {
  周:7
  月:30.4375
  季:91.3125
  半年:182.625
  年:365.25
}

时间周期时长_毫秒 = do =>
  r = {}
  for [k,v] from Object.entries 时间周期时长_天
    r[k] = v*日毫秒数
  r



#Day2Quarter = require './coffee/_lib/day2quarter'
require './coffee/_lib/week_year'

以周期为单位显示时间 = {
  周:(t)=>
    w = t.getWeek()
    if w < 10
      w = "0"+w
    t.getWeekYear()+"|"+w
  月: (t)=>
    t.toISOString()[..6]
  季:(t)=>
    t.getFullYear() + "Q" + parseInt((t.getMonth()+3)/3)
  半年:(t)=>
    t.getFullYear()+ "H" + (parseInt(t.getMonth()/6)+1)
  年:(t)=>
    t.getFullYear() + ''
}


订单数组格式化 = (
  订单分组周期
  [order_li,user_create_time]
  elem
  group_pos
  timemode
  weekuser
)->

  if not order_li.length
    return []

  group_pos = group_pos - 0
  _time = (u)->
    if group_pos
      r = u[2]
    else
      r = user_create_time[u[0]]
    new Date(r)

  # 会员号 注册时间 下单时间 金额
  order_li.sort(
    (p1,p2)->
      a = _time(p1)
      b = _time(p2)
      if a>b
          return 1
      if a==b
        if 0==group_pos
          if p1[2] > p2[2]
            return 1
          else
            return -1
        return 0
      return -1
  )


  log = []
  if 相对时间 == timemode
    周期时长 = 时间周期时长_毫秒[订单分组周期]
    时间归一化 = (下单时间, group_time)=>
      parseInt(
        (下单时间 - group_time)/周期时长
      )
    # 用来隐藏不完整的周期的复购率：因为熊猫不走有预订单，本来想的代码逻辑是取csv表格最后一个月作为订单的截止月，现在看来还需要加上一条逻辑，就是如果截止月大于当前月，就以上个月底为截止月
    end_time = _time order_li[order_li.length-1]
    if end_time > new Date
      today = new Date
      end_time = new Date(today.getFullYear(),today.getMonth(),0)
    log.meta = end_time.getFullYear()*12+end_time.getMonth()
  else
    时间归一化 = 以周期为单位显示时间[订单分组周期]

  t = []
  user_id_group = new Map()

  for i in order_li
    [user_id, amount, 下单时间] = i

    if user_id_group.has user_id
      group_time = user_id_group.get(user_id)
    else
      group_time = _time(i)
      user_id_group.set(user_id, group_time)

    if weekuser
      year = group_time.getFullYear().toString()
      week = parseInt( (group_time - new Date(year))/WEEK_MS )
      if week<10
        week = "0"+week
      group_month = year+"·"+week
    else
      group_month = group_time.toISOString().slice(0,7)

    t.push [
      group_month
      时间归一化 new Date(下单时间),group_time
      user_id
      amount-0
    ]

  for [date, li] in group_li(t)
    user_id_set = new Set()
    for [order_month, user_id, amount] in li
        user_id_set.add(user_id)

    user_count = user_id_set.size
    gli = group_li(li)
    log.push [gli, user_count, date]

  if 绝对时间左对齐 == timemode
    减 = 绝对时间减法 Math.round(365/时间周期时长_天[订单分组周期])
    for [li, count, date] from log
      date = 时间归一化(new Date(date))
      for i,pos in li
        i[0] = 减(
          i[0]
          date
        )
        null

  return log


profile_dict = {
bill_cost:(m_li, user_count)->
    t = {}
    for [order_month, li] in m_li
        total = 0
        for [user_id, amount] in li
            total += amount
        t[order_month] = Math.round(10*total/li.length)/10
    return t


user_count:(m_li, user_count)->
  t = {}
  for [order_month, li] in m_li
      month_user_id_set = new Set()
      total = 0
      for [user_id, amount] in li
          total += amount
          month_user_id_set.add(user_id)
      t[order_month] = month_user_id_set.size
  return t

amount_sum:(m_li, user_count)->
  t = {}
  for [order_month, li] in m_li
      total = 0
      for [user_id, amount] in li
          total += amount
      t[order_month] = Math.round(total/100)/100
  return t

user_cost:(m_li, user_count)->
  t = {}
  for [order_month, li] in m_li
      month_user_id_set = new Set()
      total = 0
      for [user_id, amount] in li
          total += amount
          month_user_id_set.add(user_id)
      t[order_month] = Math.round(10*total / month_user_id_set.size)/10
  return t

bill_n : (m_li, user_count)->
    t = {}
    for [order_month, li] in m_li
        month_user_id_set = new Set()
        for [user_id, amount] in li
            month_user_id_set.add(user_id)
        t[order_month] = Math.round(1000*li.length / month_user_id_set.size)/1000
    return t


rebuy : (m_li, user_count, 用户分组时间段, end_time)->
  if end_time
    [uy,um] = 用户分组时间段.split("-")
    u = uy*12+(um-1)
    月 = 分组周期_月[@订单分组周期]
    if 月
      diff = end_time - u
      if 月 > diff
        return
      max = parseInt diff/月
      m_li = m_li.filter ([order_month])=>order_month<max

  t = {}
  for [order_month, li] in m_li
    month_user_id_set = new Set()
    for [user_id, amount] in li
        month_user_id_set.add(user_id)
    t[order_month] = Math.round(1000*month_user_id_set.size / user_count)/10

  return t
}

分组周期_月 = {
  年:12
  半年:6
  季:3
}

import vue2Dropzone from 'vue2-dropzone'

_table = (csv, timemode)->
    r = [
        [
          ''
          ''
          ''
          '中位数'
        ]
        [
          ''
          ''
          ''
          '平均值'
        ]
    ]
    col = new Set()
    for [m, d] in csv
        for mc, v of d
          #if 相对时间 == timemode
          #  mc = parseInt(mc)
          col.add mc
    col = [...col]
    if timemode
        col.sort((a,b)=>a-b)
    else
        col.sort()

    r.push ['中位数','平均值','用户数',''].concat(col)

    cold = {}

    for [m, d, count] in csv
        li = [count, m]
        t = []
        total = 0
        for c in col
          if c of d
            i = d[c]
            total += i
            li.push i
            t.push i
            cold[c] = cold[c] or []
            cold[c].push i
          else
            li.push ''
        t.sort((a,b)=>a-b)
        len = t.length
        r.push(
          [
            t[parseInt(len/2)]
            Math.round(100*total/len)/100
          ].concat li
        )

    for i in col
      total = 0
      li = cold[i]
      len = li.length
      for j in li
        total+=j
      r[0].push(li[parseInt(len/2)])
      r[1].push(Math.round(total*100/len)/100)

    r

_table_html = (table)->
    _ = $.html()
    _ "<table>"
    for i, pos in table
        _ "<tr>"
        if pos != 2
            tag = 'd'
        else
            tag = 'h'
        for j in i
            _ """<t#{tag}>#{j}</t#{tag}>"""
        _ "</tr>"
    _ "</table>"
    _.html()


相对时间 = 1
绝对时间左对齐 = 2
CSV = 0

refresh = ->
  if not CSV
    return
  elem = $(@$el)
  {group_pos} = @
  _li = 订单数组格式化(@订单分组周期, CSV, elem, group_pos, @timemode, @weekuser)
  en_cn = []
  _ = $.html()
  for i in """rebuy 复购率
bill_n 人均订单
bill_cost 订单均价
user_cost 人均开销
amount_sum 收入（万元）
user_count 用户数量""".split("\n")
      [en, cn] = i = i.split(" ")
      en_cn.push i
      _ """<h2>#{cn}</h2><div class="bk #{en}"></div><canvas id=chart_#{en}></canvas><div class="chart_title">#{cn}</div>"""
  elem.find('.chartLi').html _.html()

  {timemode} = @
  for [en,cn] in en_cn
    func = profile_dict[en]
    li = []
    for i in _li
      [gli, user_count, date] = i
      r = func.call(@, ...i, _li.meta)
      if r
        li.push [date, r, user_count]

    if cn == "复购率" # 首月是100%，删掉，图表更好看
      for [month,d] in li
        if timemode
          key = 0
        else
          key = month
        delete d[key]

    table = _table(li, @timemode)

    bk = elem.find('.bk.'+en)

    if not bk[0]
      return

    bk.html _table_html(table)
    render('chart_'+en, li, @timemode)
  return

export default {
    watch:{
      group_pos: refresh
      订单分组周期 : refresh
      weekuser : refresh
      timemode : refresh
    }
    methods:{
    }
    components: {
        vueDropzone: vue2Dropzone
    }
    data :->
      分组依据 = [ [0,'注册'],[1,'首单'] ]
      self = @
      {
          id:0
          weekuser: 0
          订单分组周期:'月'
          所有订单分组周期:Object.keys(时间周期时长_毫秒)
          分组依据:分组依据[..]
          name:0
          group_pos:1
          timemode: 0
          err:""
          length:0
          ignore_no_money:0
          dropzoneOptions: {
            url:"#"
            autoProcessQueue:false
            maxFiles:1
            acceptedFiles:".csv"
            addedfile:(file)->
              self.name = 0
              CSV = 0
              @分组依据 = 分组依据[..]
              Papa.parse(file, {
                complete:(results)->
                  CSV = 格式化(results, self)
                  self.length = CSV[0].length
                  self.ignore_no_money = CSV[2]
                  self.name = file.name.slice(0,-4)
                  if CSV
                    self.$nextTick =>
                      refresh.call(self)
                  return
              })
              @removeAllFiles(true)
              return
        }
      }
}

</script>
